
#include "settings.hpp"
#include "simulator.hpp"
#include <ctime>
#include <sstream>
#include <limits>
#include <cstdlib> //system()
#ifdef CAVITY_MULTITHREADING_AVAILABLE
  #include <boost/thread/thread.hpp>
#endif

namespace settings
{
	std::map<std::string,std::string> usr_opt;
	std::string output_dir;
	std::string bitmap_path;

	//User-specific variables and flags

	long double dt = 0.24;
	int load_mode = -1;
	unsigned int mode = 0;
	unsigned int nthreads =
        #ifdef CAVITY_HAS_HARDWARE_CONCURRENCY_FUNCTION
            boost::thread::hardware_concurrency();
        #else
            0;
        #endif
	unsigned long int sub_period = 5;
	unsigned long int norm_period = sub_period * sub_period;
	unsigned int n = 0;
    std::string mrindex;
    std::string Kd_key_path = "dielectric_key.dat";
    std::string TEbound_path = "TEbound.dat";
	bool TEmode = false;   bool frequency = true;
	bool TMmode = true;    bool check = true;
	bool TEbound = true;   bool extended_check = true;
	bool boundary = true;  bool multirun_frequency = false;
	bool ffactor = true;   bool qfactor = true;
	bool wait = false;

	//Exceptions

	class no_input_file {};
	class no_setting {};
	class bad_setting {};

	//etc.

    //TODO: Patch up the path system.
    //enumerator in header:
    //enum { RESULT, BOUNDARY, FREQUENCY, CHECK_RATIO, CHECK_ACTUAL, CHECK_EXPECTED, QFACTOR, MR_FREQ };
    const unsigned int num_paths = 8;
	const char* pth[num_paths] = { "result.dat", "boundary.dat", "frequency.dat",
                       "check_ratio.dat", "check_actual.dat", "check_expected.dat",
                       "quality_factor.dat", "multirun_frequencies.dat" };
	std::vector<std::string> paths(pth, &pth[num_paths]);
    std::map<color,grid::data_type> colors;
    const color ignored_color = 0x000000;

    template<class T>
    bool get_setting( const std::string & name, T & value );
	void set_options(int argc, char* argv[]);
	std::string time_dir_name(std::time_t targ);
	std::string & create_output_dir(const std::string & dir_name = std::string());
	void load_colors();
	void cleanup();
	void change_mode_path(unsigned int mode);
	void check_parameters();
	int read_commands(int argc, char* argv[], unsigned int begin_index);
}

template<class T>
bool settings::get_setting( const std::string & name, T & value )
{
	std::map<std::string,std::string>::iterator iter = usr_opt.find(name);
	if( iter == usr_opt.end() )
		return false;
	std::istringstream iss( iter->second );
	usr_opt.erase(iter);
	return iss >> std::boolalpha >> value;
}

void settings::set_options(int argc, char* argv[])
{
	if(argc < 2)
		throw no_input_file();
	bitmap_path = argv[1];

	int out_index = read_commands(argc, argv, 2);
	create_output_dir( out_index == -1 ? std::string() : argv[out_index] );
 
    #define SET_VARIABLE( x ) { get_setting( (#x) , (x) ); output << (#x) << ": " << std::boolalpha << x << '\n'; }
    
	SET_VARIABLE( n );            SET_VARIABLE( dt );                 SET_VARIABLE( sub_period );
	SET_VARIABLE( norm_period );  SET_VARIABLE( nthreads );           SET_VARIABLE( frequency );
	SET_VARIABLE( check );        SET_VARIABLE( extended_check );     SET_VARIABLE( load_mode );
	SET_VARIABLE( mrindex );      SET_VARIABLE( multirun_frequency ); SET_VARIABLE( TEmode );
	SET_VARIABLE( boundary );     SET_VARIABLE( TEbound );            SET_VARIABLE( TMmode );
	SET_VARIABLE( mode );         SET_VARIABLE( ffactor );            SET_VARIABLE( qfactor );
	SET_VARIABLE( TEbound_path ); SET_VARIABLE( Kd_key_path );		  SET_VARIABLE( wait );
    #undef SET_VARIABLE
    //each get_setting() removes a corresponding map entry, so the remaining entries are useless.
    for(std::map<std::string,std::string>::iterator iter = usr_opt.begin(); iter != usr_opt.end(); ++iter)
    	output << "Warning: \"" << iter->first << "\" is not a valid symbol." << std::endl;

    if(nthreads < 2)
        output << "One thread will be used." << std::endl;
    else
        output << nthreads << " threads will be used." << std::endl;

	load_colors();
	change_mode_path(0); //"result.txt" becomes "result0.txt", etc.
	std::cout << std::scientific << std::setprecision(grid::precision);
}

int settings::read_commands(int argc, char* argv[], unsigned int begin_index) //returns index of output directory or -1
{
	for(int i = begin_index; i < argc; ++i)
	{
		std::string piece = argv[i];
		try {
			if( piece[0] != '-' )
				throw no_setting();

			if( piece[1] == '~' && piece.size() > 2)
				usr_opt[ piece.substr(2) ] = "false";
			else if( piece.find('=') != std::string::npos )
			{
				piece.erase(piece.begin()); //no more '-'
				std::istringstream iss(piece);
				std::string name, value;
				std::getline( iss, name, '=' );
				iss >> value;
				if( name.empty() || value.empty() )
					throw bad_setting();
				usr_opt[ name ] = value;	//usr_opt is a map<string,string>
			}
			else
				usr_opt[ piece.substr(1) ] = "true";
		}
		catch( bad_setting )
		{ output << "Bad setting: " << piece << std::endl; }
		catch( no_setting )
		{
			if(i < argc-1)
				output << "Bad setting: " << piece << std::endl;
			else
				return i; //returns the index of the next non-command argument
		}
	}//for

return -1;
}

std::string & settings::create_output_dir(const std::string & dir_name)
{
    using std::string;
    const string marker = "*TIME*";
    string temp;
    if(dir_name.empty())
        temp = marker;  //default path is the time-based folder
    else
        temp = dir_name;

    std::size_t tpos = temp.find(marker);
	if(tpos != string::npos)
	{
		temp.erase( tpos, marker.size() );
		temp.insert( tpos, time_dir_name(std::time(0)) );
	}

	//Now we create the directory, one level at a time.
    //In Windows, we can 'mkdir a/b/c' and a, b, and c will
    //  be created. However, in POSIX, an error will throw
    //  unless a and b already exist. To cirumvent this,
    //  we create a, then a/b, and then a/b/c .
    //But first we need the char that separates directory names:
    #if defined _WIN32
      const char dir_char = '\\';
    #elif defined __linux or defined __unix
      const char dir_char = '/';
    #else
        #error Unknown system: modify pertinent code in this block.
        //Choose an appropriate value for dir_char on your system.
        //dir_char is the character that separates directory names
        // e.g. c:\users\admin   --> '\\'
        // e.g. /home/guest      --> '/'
    #endif

    std::istringstream iss(temp);
    std::string buf;
    std::string call = "mkdir ";
    while( std::getline(iss, buf, dir_char) )
    {
        call += buf + dir_char;
        int sys_result = std::system( call.c_str() );
    #ifndef NDEBUG
        output << "Result of system call \"" << call << "\": " << sys_result << std::endl;
    #endif
    }

    //append dir_char so that output_dir can easily prepend output file names.
    //  (e.g. change_mode_path())
    if( *(temp.rbegin()) != dir_char)
        temp += dir_char;

    output_dir = temp; //Make it matter!

    return output_dir; //for convenience
}

std::string settings::time_dir_name( std::time_t targ )
{
    std::tm * tstruct = localtime( &targ );
    char buf[50];

    //The order of the format below chosen such that alphanumeric ordering is chronological ordering.
    if(!std::strftime( buf, 50, "%Y.%m.%d.%H.%M.%S", tstruct ))
    {
        output << "Could not make time-based directory. Using 'out' instead." << std::endl;
        return "out";
    }

    return buf;
}

void settings::load_colors()
{
    colors[0xFFFFFF] = 1.0;      //vaccuum
    //Some useful values:
    //	dry air at 1 atm, 20 degrees C:	1.00059
    //	TiO2, on average: 110.0
    std::ifstream reader(Kd_key_path.c_str());
    if(!reader)
        return;
    color c;
    grid::data_type Kd;   //dielectric constant
    while(reader >> std::hex >> c >> std::dec >> Kd)
    {
        colors[c] = Kd;
    #ifndef NDEBUG
        output << "color: " << std::hex << c
                  << "\tdielectric constant: " << std::dec << Kd
                  << std::endl;
    #endif
    }
}

//Appends the mode number to the end of output files.
void settings::change_mode_path(unsigned int mode)
{
    for(unsigned int i = 0; i < paths.size(); ++i)
    {
        std::ostringstream ss;
        ss << mode;
        std::string tmp(pth[i]);
        tmp.insert(tmp.rfind('.'),ss.str());
        paths[i] = output_dir + tmp;
    }
}

void settings::cleanup()
{
}

struct compare_color_Kd //for use in check_parameters()
{
	typedef std::map<color,grid::data_type>::value_type cmvt;
	bool operator()(const cmvt & l, const cmvt & r) { return l.second < r.second; }
};

void settings::check_parameters()
{
    //  A multiplier of 0.25 or greater leads to instability in the
    //  relaxation step, so we must ensure that dt*max(Kd) < 0.25
    //  Even double(0.249) has caused trouble on some processors.

    grid::data_type maxKd = std::max_element( colors.begin(), colors.end(), compare_color_Kd() )->second;
    if( dt * maxKd >= 0.25 )
    {
        grid::data_type suggested_dt = 0.24 / maxKd;
        std::ostringstream oss;
        oss << "dt * maxKd >= 0.25; instability will result.\n"
            << "Suggest a dt of " << suggested_dt << " for a relaxation factor of 0.24";
        throw std::string(oss.str());
    }
    else if( dt * maxKd > 0.24 )
        output << "Caution: dt * maxKd > 0.24\n\tStrange behavior could result."<< std::endl;

    //  If the user wishes to load the first n modes from
    //  the disk and then calculate the remaining modes up to m,
    //  we must ensure that m > n.
    if(static_cast<int>(mode) <= load_mode)
        throw std::string("load_mode must be < mode. Maybe you didn't '-mode=' ?");

    //  norm_period must be a multiple of sub_period.
    //  This allows us to avoid having two branches every cycle. Instead we
    //  have one branch every cycle, and then a second every sub_period'th
    //  cycle. First, though, neither can be zero.
	if(norm_period == 0 || sub_period == 0)
		throw std::string("neither norm_period nor sub_period may be zero.");
    else if(norm_period % sub_period != 0)
        throw std::string("normalization_period must be a multiple of subtraction_period.");

    #ifndef CAVITY_MULTITHREADING_AVAILABLE
    nthreads = 0;
    #endif
    //This makes irrelevant the check in cavity.cpp
    //Keep?
}

